package com.powernode.config;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.powernode.constant.AuthConstant;
import com.powernode.constant.HttpConstant;
import com.powernode.constant.ResponseCode;
import com.powernode.model.LoginResult;
import com.powernode.model.Result;
import com.powernode.service.impl.UserDetailsServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import java.time.Duration;
import java.util.UUID;


/**
 * @author syo 2319615164@qq.com
 * @description security安全框架配置
 * @date 2024/3/31
 * @Copyright syo
 */
@Configuration
@RequiredArgsConstructor
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsServiceImpl userDetailsService;

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 结合数据做认证（让安全框架走自己的认证流程）
     * @param auth          认证管理器
     * @throws Exception    异常
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
         auth.userDetailsService(userDetailsService);
    }

    /**
     * 配置安全策略
     * @param http          http对象
     * @throws Exception    异常
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 1. 关闭跨站请求伪造
        http.cors().disable();

        // 2. 关闭跨域
        http.csrf().disable();

        // 3. 关闭session使用策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // 4. 配置登录信息
        http.formLogin()
                .loginProcessingUrl(AuthConstant.LOGIN_URL)
                .successHandler(authenticationSuccessHandler())
                .failureHandler(authenticationFailureHandler());

        // 5. 配置登出信息
        http.logout()
                .logoutUrl(AuthConstant.LOGOUT_URL)
                .logoutSuccessHandler(logoutSuccessHandler());

        // 6. 所有请求都需要进行身份认证
        http.authorizeHttpRequests().anyRequest().authenticated();
    }

    // 将认证管理器放到容器中
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 登录成功处理器
     * @return AuthenticationSuccessHandler
     */
    @Bean
    public AuthenticationSuccessHandler authenticationSuccessHandler() {
        return (request, response, authentication) -> {
            // 1. 设置响应头
            response.setContentType(HttpConstant.APPLICATION_TYPE);
            response.setCharacterEncoding(HttpConstant.UTF_8);

            // 2. 使用UUID作为TOKEN
            String token = UUID.randomUUID().toString();
            Object principal = authentication.getPrincipal();

            // 3. 转换为JSON
            String userDetailJsonStr = JSONUtil.toJsonStr(principal);

            // 4. 设置到Redis
            stringRedisTemplate
                    .opsForValue()
                    .set(AuthConstant.LOGIN_TOKEN_PREFIX + token, userDetailJsonStr,
                            Duration.ofSeconds(AuthConstant.TOKEN_TIME));

            // 5. 响应
            Result<LoginResult> res = Result.success(new LoginResult(token, AuthConstant.TOKEN_TIME));

            // 6. 返回
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(response.getOutputStream(), res);
        };
    }

    /**
     * 登录失败处理器
     * @return AuthenticationFailureHandler
     */
    @Bean
    public AuthenticationFailureHandler authenticationFailureHandler() {
        return (request, response, exception) -> {
            // 1. 设置响应头
            response.setContentType(HttpConstant.APPLICATION_TYPE);
            response.setCharacterEncoding(HttpConstant.UTF_8);

            // 2. 结果
            Result<Object> res = new Result<>();
            res.setCode(ResponseCode.OPERATION_FAIL.getCode());
            if (exception instanceof BadCredentialsException) {
                res.setMsg("用户名或密码有误");
            } else if (exception instanceof UsernameNotFoundException) {
                res.setMsg("用户名不存在");
            } else {
                res.setMsg("账户异常");
            }

            // 3. 写回
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(response.getOutputStream(), res);
        };
    }

    /**
     * 登出成功处理器
     * @return LogoutSuccessHandler
     */
    @Bean
    public LogoutSuccessHandler logoutSuccessHandler() {
        return (request, response, authentication) -> {
            // 1. 设置响应头
            response.setContentType(HttpConstant.APPLICATION_TYPE);
            response.setCharacterEncoding(HttpConstant.UTF_8);

            // 2. 从请求头中获取TOKEN
            String authorization = request.getHeader(AuthConstant.AUTH_HEAD);
            String token = authorization.replaceFirst(AuthConstant.AUTH_BEARER, "");
            stringRedisTemplate.delete(AuthConstant.LOGIN_TOKEN_PREFIX + token);

            // 3. 登出成功
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.writeValue(response.getOutputStream(), Result.success(null));
        };
    }
}
